#include "board.h"
#include "uart.h"

rt_uint8_t RxBuffer[RT_CONSOLEBUF_SIZE];
rt_uint8_t uart_rx_buf[RT_CONSOLEBUF_SIZE] = {0};
static struct rt_ringbuffer  uart_rxcb;
static struct rt_semaphore shell_rx_sem;

rt_inline enum rt_ringbuffer_state rt_ringbuffer_status(struct rt_ringbuffer *rb)
{
    if (rb->read_index == rb->write_index)
    {
        if (rb->read_mirror == rb->write_mirror)
            return RT_RINGBUFFER_EMPTY;
        else
            return RT_RINGBUFFER_FULL;
    }
    return RT_RINGBUFFER_HALFFULL;
}

/**
 * get the size of data in rb
 */
rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb)
{
    switch (rt_ringbuffer_status(rb))
    {
    case RT_RINGBUFFER_EMPTY:
        return 0;
    case RT_RINGBUFFER_FULL:
        return rb->buffer_size;
    case RT_RINGBUFFER_HALFFULL:
    default:
        if (rb->write_index > rb->read_index)
            return rb->write_index - rb->read_index;
        else
            return rb->buffer_size - (rb->read_index - rb->write_index);
    };
}

void rt_ringbuffer_init(struct rt_ringbuffer *rb,
                        rt_uint8_t           *pool,
                        rt_int16_t            size)
{
    RT_ASSERT(rb != RT_NULL);
    RT_ASSERT(size > 0);

    /* initialize read and write index */
    rb->read_mirror = rb->read_index = 0;
    rb->write_mirror = rb->write_index = 0;

    /* set buffer pool and size */
    rb->buffer_ptr = pool;
    rb->buffer_size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);
}

/**
 * put a character into ring buffer
 */
rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch)
{
    RT_ASSERT(rb != RT_NULL);

    /* whether has enough space */
    if (!rt_ringbuffer_space_len(rb))
        return 0;

    rb->buffer_ptr[rb->write_index] = ch;

    /* flip mirror */
    if (rb->write_index == rb->buffer_size-1)
    {
        rb->write_mirror = ~rb->write_mirror;
        rb->write_index = 0;
    }
    else
    {
        rb->write_index++;
    }

    return 1;
}
/**
 * get a character from a ringbuffer
 */
rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch)
{
    RT_ASSERT(rb != RT_NULL);

    /* ringbuffer is empty */
    if (!rt_ringbuffer_data_len(rb))
        return 0;

    /* put character */
    *ch = rb->buffer_ptr[rb->read_index];

    if (rb->read_index == rb->buffer_size-1)
    {
        rb->read_mirror = ~rb->read_mirror;
        rb->read_index = 0;
    }
    else
    {
        rb->read_index++;
    }

    return 1;
}

void uart_init(void)
{
    rt_ringbuffer_init(&uart_rxcb, uart_rx_buf, RT_CONSOLEBUF_SIZE);
    rt_sem_init(&(shell_rx_sem), "shell_rx", 0, 0);

    USARTx_GPIO_CLK_ENABLE();
    USARTx_CLK_ENABLE();

    /* Configure Tx Pin as : Alternate function, High Speed, Push pull, Pull up */
    LL_GPIO_SetPinMode(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_MODE_ALTERNATE);
    LL_GPIO_SetPinSpeed(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_SPEED_FREQ_HIGH);
    LL_GPIO_SetPinOutputType(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_OUTPUT_PUSHPULL);
    LL_GPIO_SetPinPull(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_PULL_UP);

    /* Configure Rx Pin as : Input Floating function, High Speed, Pull up */
    LL_GPIO_SetPinMode(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_MODE_FLOATING);
    LL_GPIO_SetPinSpeed(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_SPEED_FREQ_HIGH);
    LL_GPIO_SetPinPull(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_PULL_UP);

    LL_USART_Disable(USARTx_INSTANCE);
    LL_USART_SetTransferDirection(USARTx_INSTANCE, LL_USART_DIRECTION_TX_RX);
    LL_USART_ConfigCharacter(USARTx_INSTANCE, LL_USART_DATAWIDTH_8B, LL_USART_PARITY_NONE, LL_USART_STOPBITS_1);
    LL_USART_SetBaudRate(USARTx_INSTANCE, SystemCoreClock/APB_Div, 115200);
    LL_USART_Enable(USARTx_INSTANCE);

    /* DMA1 used for USART2 Transmission and Reception */
    LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);

    NVIC_SetPriority(DMA1_Channel7_IRQn, 0);
    NVIC_EnableIRQ(DMA1_Channel7_IRQn);
    NVIC_SetPriority(DMA1_Channel6_IRQn, 0);
    NVIC_EnableIRQ(DMA1_Channel6_IRQn);
    
    LL_DMA_ConfigTransfer(DMA1, LL_DMA_CHANNEL_7, 
                LL_DMA_DIRECTION_MEMORY_TO_PERIPH | 
                LL_DMA_PRIORITY_HIGH              | 
                LL_DMA_MODE_NORMAL                | 
                LL_DMA_PERIPH_NOINCREMENT         | 
                LL_DMA_MEMORY_INCREMENT           | 
                LL_DMA_PDATAALIGN_BYTE            | 
                LL_DMA_MDATAALIGN_BYTE);
    LL_DMA_ConfigTransfer(DMA1, LL_DMA_CHANNEL_6, 
                        LL_DMA_DIRECTION_PERIPH_TO_MEMORY | 
                        LL_DMA_PRIORITY_HIGH              | 
                        LL_DMA_MODE_NORMAL                | 
                        LL_DMA_PERIPH_NOINCREMENT         | 
                        LL_DMA_MEMORY_INCREMENT           | 
                        LL_DMA_PDATAALIGN_BYTE            | 
                        LL_DMA_MDATAALIGN_BYTE);
    LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_6,
                            LL_USART_DMA_GetRegAddr(USARTx_INSTANCE),
                            (uint32_t)RxBuffer,
                            LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_6));
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_6, RT_CONSOLEBUF_SIZE);

    // dma tx irq enable
    LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_7);
    LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_7);

    NVIC_SetPriority(USARTx_IRQn, 0);  
    NVIC_EnableIRQ(USARTx_IRQn);
    
    // LL_USART_EnableIT_RXNE(USARTx_INSTANCE);
    LL_USART_EnableIT_IDLE(USARTx_INSTANCE);
    LL_USART_EnableIT_ERROR(USARTx_INSTANCE);
    LL_USART_EnableDMAReq_RX(USARTx_INSTANCE);
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_6);
}

void DMA1_Channel7_IRQHandler(void)
{
    if (LL_DMA_IsActiveFlag_TC7(DMA1))
    {
        LL_DMA_ClearFlag_TC7(DMA1);
        LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_7);
    }
    else if (LL_DMA_IsActiveFlag_TE7(DMA1))
    {
        LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_7);
    }
}

void USARTx_IRQHandler(void)
{
    rt_uint8_t received = 0,num = 0;

    if(LL_USART_IsActiveFlag_RXNE(USARTx_INSTANCE) && LL_USART_IsEnabledIT_RXNE(USARTx_INSTANCE))
    {
        LL_USART_ClearFlag_RXNE(USARTx_INSTANCE);
        received = LL_USART_ReceiveData8(USARTx_INSTANCE);
        LL_USART_TransmitData8(USARTx_INSTANCE, received);
        while (!LL_USART_IsActiveFlag_TC(USARTx_INSTANCE));
    }
    else if(LL_USART_IsActiveFlag_IDLE(USARTx_INSTANCE) && LL_USART_IsEnabledIT_IDLE(USARTx_INSTANCE))
    {
        LL_USART_ClearFlag_IDLE(USARTx_INSTANCE);
        LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_6);

        received = RT_CONSOLEBUF_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_6);
        RxBuffer[received] = 0;
        while(received--)rt_ringbuffer_putchar(&uart_rxcb, RxBuffer[num++]);

        LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_6, RT_CONSOLEBUF_SIZE);
        LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_6);
        rt_sem_release(&shell_rx_sem);
    }
    else
    {
        NVIC_DisableIRQ(USARTx_IRQn);
        rt_kprintf("uart rx irq err.\n");
    }
}

void uart_dma_send(const char *str)
{
    if(LL_DMA_IsEnabledChannel(DMA1, LL_DMA_CHANNEL_7) != 0)
        return;

    LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_7,
                    (uint32_t)str,
                    LL_USART_DMA_GetRegAddr(USART2),
                    LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_7));
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_7, strlen(str));

    /* Enable DMA TX Interrupt */
    LL_USART_EnableDMAReq_TX(USART2);
    /* Enable DMA Channel Tx */
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_7);
}

#ifdef RT_USING_CONSOLE

void rt_hw_console_output(const char *str)
{
    while(*str != '\0')
    {
        while (!LL_USART_IsActiveFlag_TXE(USARTx_INSTANCE));
        LL_USART_ClearFlag_TC(USARTx_INSTANCE);
        LL_USART_TransmitData8(USARTx_INSTANCE, *str++);
        while (!LL_USART_IsActiveFlag_TC(USARTx_INSTANCE));
    }
}

char rt_hw_console_getchar(void)
{
    char ch = 0;

    while (rt_ringbuffer_getchar(&uart_rxcb, (rt_uint8_t *)&ch) != 1)
    {
        rt_sem_take(&shell_rx_sem, RT_WAITING_FOREVER);
    }
    return ch;
}

#endif

#if 0

static void uart_test(void)
{
    int32_t tick = 0;

    tick = LL_TIM_GetCounter(MeasureTIM);
	rt_kprintf("hello,this is a uart test.\n");
    rt_kprintf("time:%d\n",tick - LL_TIM_GetCounter(MeasureTIM));

    tick = LL_TIM_GetCounter(MeasureTIM);
    uart_dma_send("hello,this is a uart dma test.\n");
    rt_kprintf("time:%d\n",tick - LL_TIM_GetCounter(MeasureTIM));

}
MSH_CMD_EXPORT(uart_test, uart_test);

#endif
